import dayjs from 'dayjs'
import { unionBy } from 'lodash-es'

import { datasource } from '../../db/index.js'
import { isEmpty } from '../../utils/index.js'

import { covertToTree, transformTreeAndSetProps } from './help.js'

// 实体
import menuModel from '../../model/menuModel.js'
import userModel from '../../model/userModel.js'
import roleModel from '../../model/roleModel.js'

const menuRepository = datasource.getRepository(menuModel)
const userRepository = datasource.getRepository(userModel)
const roleRepository = datasource.getRepository(roleModel)

/**
 * 查询节点排序是否重复
 */
const findNodeOrderRepeat = async (body) => {
  // 查询出相同parentId的中，非逻辑删除的 且menuOrder相同的数据，如果存在，则不让排序重复
  let sameOrderItem = await menuRepository
    .createQueryBuilder('menu')
    .where({
      parentId: body.parentId
    })
    .andWhere('menu.menuOrder = :menuOrder', {
      menuOrder: body.menuOrder
    })
    .andWhere('menu.isDelete = :isDelete', {
      isDelete: 0
    })
    .getOne()

  return sameOrderItem
}

export const getMenuList = async (req, res) => {
  let result = await menuRepository
    .createQueryBuilder('menu')
    .where('menu.isDelete=:isDelete', {
      isDelete: 0
    })
    .orderBy({
      // 根据菜单进行升序排列
      menuOrder: 'ASC'
    })
    .getMany()

  result.forEach(item => {
    delete item.updateTime
    delete item.createTime
    delete item.createById
  })

  let tree = covertToTree(result, 0)

  res.json({
    code: 1000,
    data: tree || [],
    message: '操作成功'
  })
}

/**
 * 新增菜单
 */
export const createMenu = async (req, res) => {
  // 需要验证 menuOrder 不能重复
  const body = req.body

  // 第一层菜单
  if (body.parentId === '' || body.parentId === null) {
    body.parentId = 0
    body.parentIds = '[0]'
  }

  try {
    let sameOrderItem = await findNodeOrderRepeat(body)

    if (sameOrderItem) {
      res.json({
        code: -1,
        data: null,
        message: `名称： ${body.menuName} 排序重复`
      })
      return
    }

    body.isDelete = 0

    // 新增前端可能会传一个空的菜单id, 实际是不需要的，数据库会自增
    delete body.menuId

    // 其他层级
    if (body.parentId && body.parentId !== 0) {
      // 根据parentId 查询到父节点数据-需要拿到父节点的 parentIds 进行拼接，作为当前节点的parentIds
      let parentRow = await menuRepository
        .createQueryBuilder('menu')
        .select(['menu.menuId', 'menu.isDelete', 'menu.parentIds'])
        .where('menu.menuId=:menuId', {
          menuId: body.parentId
        })
        .getOne()

      if (!parentRow) {
        res.json({
          code: -1,
          data: null,
          message: `parentId 父节点不存在- ${body.parentId}`
        })

        return
      }

      if (parentRow.isDelete === 1) {
        res.json({
          code: -1,
          data: null,
          message: '父节点已经被删除，请刷新重试'
        })
        return
      }

      body.parentIds = `${parentRow.parentIds},[${body.parentId}]`
    }

    // 插入数据
    await menuRepository
      .createQueryBuilder()
      .insert()
      .into(menuModel)
      .values([
        {
          ...body,
          createById: req.userId,
          updateId: req.userId
        }
      ])
      .execute()

    res.json({
      code: 1000,
      data: null,
      message: '操作成功'
    })
  } catch (error) {
    console.log(error)
    res.json({
      code: -1,
      data: null,
      message: '操作失败' + error.message
    })
  }
}

export const updateMenu = async (req, res) => {
  let body = req.body
  try {
    // 当前编辑的是根节点-不需要验证父节点是否存在
    if (body.parentId !== 0) {
      // 验证一下父节点是否存在，避免其他人在同步删除父节点
      let exitParentNode = await menuRepository
        .createQueryBuilder('menu')
        .where('menu.menuId = :menuId', {
          menuId: body.parentId
        })
        .andWhere('menu.isDelete =:isDelete', {
          isDelete: 0
        })
        .getOne()

      if (!exitParentNode) {
        res.json({
          code: -1,
          data: null,
          message: '当前节点的父节点不存在，请刷新重试'
        })
        return
      }
    }

    // 排序去重处理
    let sameOrderItem = await findNodeOrderRepeat(body)

    // 排序已经存在且不为自己操作的节点
    if (sameOrderItem && body.menuId !== sameOrderItem.menuId) {
      res.json({
        code: -1,
        data: null,
        message: `名称： ${body.menuName} 排序重复`
      })
      return
    }

    let currentNode = await menuRepository.findOne({
      where: {
        menuId: body.menuId
      }
    })

    let updateNode = Object.assign({}, currentNode, req.body, {
      updateTime: dayjs().format('YYYY-MM-DD HH:mm:ss')
    })

    if (body.menuType === 4) {
      updateNode.menuName = body.buttonName
    }

    updateNode.updateId = req.userId

    await menuRepository.save(updateNode)

    res.json({
      code: 1000,
      data: null,
      message: '操作成功'
    })
  } catch (e) {
    res.json({
      code: -1,
      data: null,
      message: '操作失败' + e.message
    })
  }
}

/**
 * 删除菜单
 */
export const deleteMenu = async (req, res) => {
  let { menuId } = req.query

  if (isEmpty(menuId)) {
    res.json({
      code: -1,
      data: null,
      message: 'menuId不能为空'
    })
    return
  }

  try {
    // 查询对应的菜单和子菜单
    let item = await menuRepository
      .createQueryBuilder('menu')
      .where('menu.menuId =:menuId', {
        menuId
      })
      .getOne()

    if (!item) {
      res.json({
        code: -1,
        data: null,
        message: '菜单不存在'
      })
      return
    }

    if (item.isDelete === 1) {
      res.json({
        code: -1,
        data: null,
        message: `操作失败, 当前菜单已被删除，请刷新后重新操作`
      })
      return
    }

    // 根据parentIds 使用Like查询所有子节点相似的数据
    // [0],[1],[7],%
    // [0],[1],[7],[10] 子节点数据会被找到-需要删除的

    // 这里的Like 不能使用 .where('menu.parentIds =:parentIds', {parentIds: Like(`${item.parentIds},%`)}) 这种转义的, 会报错

    // 这里删除一定要加上当前节点自己，因为我们查询的是当前删除节点的子节点，如果不附带自身，则会误删
    // 加上我们的 parentIds 是 [0] 那么like [0],%  则会吧所有一级节点下面的其他节点都删除。 不止是这种情况 [0],[20] 对于 有N层节点的场景，则会删除同级节点
    // console.log(`menu.parentIds Like('${item.parentIds},[${item.menuId}],%')`);

    let children = await menuRepository
      .createQueryBuilder('menu')
      .where(`menu.parentIds Like('${item.parentIds},[${item.menuId}],%')`)
      .getMany()

    // 假设这里children 长度有1w个节点数据，则下面的save会比较慢，可以做分片处理

    // 如果children 长度 = 0， 则表示没有子节点，不用管，继续进行下一步，删除自身节点即可
    if (children.length) {
      children.forEach((p) => {
        p.updateId = req.userId
        p.isDelete = 1
        p.updateTime = dayjs().format('YYYY-MM-DD HH:mm:ss')
      })

      // 将子节点数据全部设置为逻辑删除
      await menuRepository.save(children)
    }

    // 删除当前自身节点
    item.isDelete = 1
    item.updateTime = dayjs().format('YYYY-MM-DD HH:mm:ss')

    item.updateId = req.userId

    await menuRepository.save(item)

    res.json({
      code: 1000,
      data: null,
      message: '操作成功'
    })
  } catch (error) {
    res.json({
      code: -1,
      data: null,
      message: `新增失败${error.message}`
    })
  }
}

/**
 * 用户详情-根据角色id查询权限树
 *
 * 菜单数组是树的叶子节点，不包含父节点
 * @returns {Array<any>} 只返回菜单
 */
export const getPermissionByRoleId = async (req, res) => {
  try {
    // 如果是超管
    if (Number(req.query.roleId) === 0) {
      let menus = await menuRepository
        .createQueryBuilder('menu')
        .where('menu.isDelete =:isDelete', {
          isDelete: 0
        })
        .getMany()

      // 转成菜单Tree
      let tree = covertToTree(menus, 0)
      res.json({
        code: 1000,
        data: tree,
        message: '操作成功'
      })

      return
    }

    const result = await roleRepository
      .createQueryBuilder('role')
      .leftJoinAndSelect('role.menus', 'menus')
      .where('role.roleId = :roleId', {
        roleId: req.query.roleId
      })
      .getOne()

    if (!result) {
      res.json({
        code: -1,
        data: null,
        message: '角色不存在'
      })
      return
    }

    // 角色下没有挂任何菜单
    if (!result.menus || result.menus.length === 0) {
      res.json({
        code: 1000,
        data: [],
        message: '操作成功'
      })
      return
    }

    // 拿到所有的叶子节点（不包含父节点的数据）， 但是 parentIds里面是存储了父节点的路径

    let parentIds = new Set()

    result.menus.forEach((node) => {
      // "[0],[40]" 存储进set里面，去重
      let ids = node.parentIds.match(/\d+/g).map((p) => Number(p))

      ids.forEach((id) => parentIds.add(id))

      // 已有权限的菜单树，需要包含当前叶子节点，不能只是上面的父节点
      parentIds.add(node.menuId)
    })

    // 移除0 , 0这个节点其实在数据库是没有的， 人为构造的，将所有根节点都挂到0下面成为子节点
    // 所以，在数据库查询中，不需要查询0
    parentIds.delete(0)

    let menuList = await menuRepository
      .createQueryBuilder('menu')
      .where('menu.menuId In(:...menuId)', {
        menuId: Array.from(parentIds)
      })
      .getMany()

    menuList.forEach((p) => {
      delete p.createTime
      delete p.updateTime
      delete p.isDelete
      delete p.createById
    })

    // 将菜单列表转成树返回给前端
    let tree = covertToTree(menuList, 0)

    res.json({
      code: 1000,
      data: tree,
      message: '操作成功'
    })
  } catch (error) {
    console.log(error)
    res.json({
      code: -1,
      data: null,
      message: '操作失败' + error.message
    })
  }
}

/**
 * 登录场景-根据用户id -查询到对应的权限树
 */
export const getPermissionListByUserId = async (req, res) => {
  // 超级管理员
  if (req.userId === 0) {
    let fullNodes = await menuRepository.createQueryBuilder('menu')
      .where('menu.isDelete =:isDelete', {
        isDelete: 0
      })
      .getMany()

    // fullNodes.forEach(item => {
    //   if(item.menuType === 1 && item.component === '') {
    //     item.component = 'Layout'
    //   }
    // })


    let tree = covertToTree(fullNodes, 0)

    res.json({
      code: 1000,
      data: tree,
      message: '操作成功'
    })
    return
  }

  // 拿到用户所有角色 + 权限

  let rows = await userRepository.createQueryBuilder('user')
    .where('user.userId =:userId', {
      userId: req.userId
    })
    .innerJoinAndSelect('user.roles', 'roles')
    .getOne()

  // 拿到启用的角色
  let roleIds = rows.roles.map(p => {
    if (p.roleId && p.status === 1) return p.roleId
  })

  if (roleIds.length === 0) {
    res.json({
      code: 1000,
      data: [],
      message: '操作成功'
    })
    return
  }

  // 根据角色把所有 末端节点菜单都查询出来
  let roleList = await roleRepository.createQueryBuilder('role')
    .where('role.roleId In(:...roleId)', {
      roleId: roleIds
    })
    .leftJoinAndSelect('role.menus', 'menus')
    .getMany()


  let menuList = []

  roleList.map(p => {
    menuList = menuList.concat(p.menus)
  })

  // 有角色，但是所有角色都没有菜单
  if (menuList.length === 0) {
    res.json({
      code: 1000,
      data: [],
      message: '操作成功'
    })
    return
  }

  // 根据菜单id做去重
  let endNodes = unionBy(menuList, 'menuId')

  let nodeIds = new Set()

  let ids = []

  // 将末端节点中的所有父节点全部找到
  endNodes.forEach(p => {
    // 首先将自身节点自身id添加到 Set中
    nodeIds.add(p.menuId)

    // 提取父节点
    ids = p.parentIds.match(/\d+/g).map((p) => Number(p))

    ids.forEach((id) => nodeIds.add(id))
  })

  // 一定要删除0， 否则会吧所有没有授权的id= 0的节点都查询出来
  nodeIds.delete(0)

  // 根据id查询出完整的节点数据
  let fullNodes = await menuRepository.createQueryBuilder('menu')
    .where('menu.menuId In(:...ids)', {
      ids: Array.from(nodeIds)
    })
    .andWhere('menu.isDelete =:isDelete', {
      isDelete: 0
    })
    .getMany()

  fullNodes.forEach(p => {
    delete p.createTime
    delete p.updateTime
    delete p.isDelete
    delete p.createById

    // 针对根节点，类型为 目录节点，必须设置一个Layout，否则菜单是单独开的
    // 前端在新增的时候，其实是传入了值的
    // 但是二级目录，需要前端在存储的时候自己设置，一般设置 blockLayout, 空的 router-view
    if (p.parentId === 0 && p.menuType === 1 && p.component === '') {
      p.component = 'layout'
    }

    return p
  })

  let tree = transformTreeAndSetProps(fullNodes, 0)

  res.json({
    code: 1000,
    data: tree,
    message: '操作成功'
  })
}